#include "StdAfx.h"

namespace CoreRay
{
	namespace DirectX
	{
		DirectXRenderAccelerator::DirectXRenderAccelerator()
		{
			this->renderTargetsCount = 2;
		}

		DirectXRenderAccelerator::~DirectXRenderAccelerator()
		{
			// Call the object finalizer to release unmanaged resources
			this->!DirectXRenderAccelerator();

			// Dispose managed resources
			if(this->geometryObjectCache != nullptr)
			{
				for each(DirectXGeometryObject^ geometryObject in this->geometryObjectCache->Values)
				{
					delete geometryObject;
				}

				this->geometryObjectCache = nullptr;
			}
		}

		DirectXRenderAccelerator::!DirectXRenderAccelerator()
		{
			//Free CPU render target textures
			if(this->cpuOutputTextureData)
			{
				delete this->cpuOutputTextureData;
				this->cpuOutputTextureData = NULL;
			}

			//Free CPU render target textures
			if(this->cpuOutputTextures)
			{
				for(int i = 0; i < this->renderTargetsCount; i++)
				{
					this->cpuOutputTextures[i]->Release();
				}

				delete this->cpuOutputTextures;
				this->cpuOutputTextures = NULL;
			}

			//Free GPU render target textures
			if(this->renderTargetTextures)
			{
				for(int i = 0; i < this->renderTargetsCount; i++)
				{
					this->renderTargetTextures[i]->Release();
				}

				delete this->renderTargetTextures;
				this->renderTargetTextures = NULL;
			}

			//Free render targets memory
			if(this->renderTargets)
			{
				for(int i = 0; i < this->renderTargetsCount; i++)
				{
					this->renderTargets[i]->Release();
				}

				delete this->renderTargets;
				this->renderTargets = NULL;
			}

			//Free depth stencil memory
			if(this->depthStencil)
			{
				this->depthStencil->Release();
				this->depthStencil = NULL;
			}

			//Free shader memory
			if(this->shader)
			{
				this->shader->Release();
				this->shader = NULL;
			}

			//Free graphicsDevice memory
			if(this->graphicsDevice)
			{
				this->graphicsDevice->Release();
				this->graphicsDevice = NULL;
			}
		}

		void DirectXRenderAccelerator::InitScene(Scene^ scene)
		{
			//Create the Direct3D device
			this->CreateDirect3DDevice();

			//Load the shader from resources
			ResourceManager^ resources = gcnew ResourceManager("CoreRayDirectX.Resources", Assembly::GetExecutingAssembly());
			StreamReader^ reader = gcnew StreamReader(gcnew MemoryStream((array<byte>^) resources->GetObject("Shader")));
			this->shader = this->CreateShader(reader->ReadToEnd());

			//Create geometry in hardware buffers
			this->geometryObjectCache = gcnew Dictionary<String^, DirectXGeometryObject^>();

			for each (SceneObject^ sceneObject in scene->SceneObjects)
			{
				Mesh^ geometryObject = dynamic_cast<Mesh^>(sceneObject);

				if(geometryObject != nullptr)
				{
					DirectXGeometryObject^ directXGeometryObject = gcnew DirectXGeometryObject(this->graphicsDevice, this->shader, geometryObject);
					this->geometryObjectCache->Add(geometryObject->Id.ToString(), directXGeometryObject);
				}
			}

			//Clear the hardware render target
			float clearColor[4] = { 0.0f, 0.0f, 0.0f, 0.0f };

			for(int i = 0; i < this->renderTargetsCount; i++)
			{
				this->graphicsDevice->ClearRenderTargetView(this->renderTargets[i], clearColor);
			}

			this->graphicsDevice->ClearDepthStencilView(this->depthStencil, D3D10_CLEAR_DEPTH, 1.0f, 0);

			//Process each geometry objects
			for each(DirectXGeometryObject^ geometryObject in this->geometryObjectCache->Values)
			{
				//Set the worldViewProjMatrix shader parameter
				Matrix^ worldViewProjMatrix = geometryObject->WorldMatrix * scene->MainCamera->ViewMatrix * scene->MainCamera->ProjectionMatrix;

				D3DXMATRIX d3dMatrix = D3DXMATRIX(worldViewProjMatrix->Element00, worldViewProjMatrix->Element01, worldViewProjMatrix->Element02, worldViewProjMatrix->Element03,
												  worldViewProjMatrix->Element10, worldViewProjMatrix->Element11, worldViewProjMatrix->Element12, worldViewProjMatrix->Element13,
										          worldViewProjMatrix->Element20, worldViewProjMatrix->Element21, worldViewProjMatrix->Element22, worldViewProjMatrix->Element23,
												  worldViewProjMatrix->Element30, worldViewProjMatrix->Element31, worldViewProjMatrix->Element32, worldViewProjMatrix->Element33);

				this->shader->GetVariableByName("worldViewProjMatrix")->AsMatrix()->SetMatrix(d3dMatrix);

				//Set the worldMatrix shader parameter
				d3dMatrix = D3DXMATRIX(geometryObject->WorldMatrix->Element00, geometryObject->WorldMatrix->Element01, geometryObject->WorldMatrix->Element02, geometryObject->WorldMatrix->Element03,
									   geometryObject->WorldMatrix->Element10, geometryObject->WorldMatrix->Element11, geometryObject->WorldMatrix->Element12, geometryObject->WorldMatrix->Element13,
									   geometryObject->WorldMatrix->Element20, geometryObject->WorldMatrix->Element21, geometryObject->WorldMatrix->Element22, geometryObject->WorldMatrix->Element23,
				                       geometryObject->WorldMatrix->Element30, geometryObject->WorldMatrix->Element31, geometryObject->WorldMatrix->Element32, geometryObject->WorldMatrix->Element33);

				this->shader->GetVariableByName("worldMatrix")->AsMatrix()->SetMatrix(d3dMatrix);

				//Set the worldNormalMatrix shader parameter
				d3dMatrix = D3DXMATRIX(geometryObject->InverseWorldMatrix->Element00, geometryObject->InverseWorldMatrix->Element10, geometryObject->InverseWorldMatrix->Element20, geometryObject->InverseWorldMatrix->Element30,
									   geometryObject->InverseWorldMatrix->Element01, geometryObject->InverseWorldMatrix->Element11, geometryObject->InverseWorldMatrix->Element21, geometryObject->InverseWorldMatrix->Element31,
									   geometryObject->InverseWorldMatrix->Element02, geometryObject->InverseWorldMatrix->Element12, geometryObject->InverseWorldMatrix->Element22, geometryObject->InverseWorldMatrix->Element32,
									   0, 0, 0, geometryObject->InverseWorldMatrix->Element33);

				this->shader->GetVariableByName("worldNormalMatrix")->AsMatrix()->SetMatrix(d3dMatrix);

				//Setup the shader
				this->shader->GetTechniqueByIndex(0)->GetPassByIndex(0)->Apply(0);

				//Setup the input layout
				this->graphicsDevice->IASetInputLayout(geometryObject->InputLayout);

				//Setup the vertex buffer
				ID3D10Buffer* internalVertexBuffer = geometryObject->VertexBuffer;
				UINT stride = geometryObject->ObjectVertexFormat->VertexSize;
				UINT offset = 0;

				this->graphicsDevice->IASetVertexBuffers(0, 1, &internalVertexBuffer, &stride, &offset);
				this->graphicsDevice->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

				//Setup the index buffer
				this->graphicsDevice->IASetIndexBuffer(geometryObject->IndexBuffer, DXGI_FORMAT_R32_UINT, 0);

				//Draw the geometry
				this->graphicsDevice->DrawIndexed(geometryObject->VertexCount, 0, 0);
			}

			//Copy the GPU render target to the CPU texture
			for(int i = 0; i < this->renderTargetsCount; i++)
			{
				//Copy data from GPU texture to CPU texture
				this->graphicsDevice->CopyResource(this->cpuOutputTextures[i], this->renderTargetTextures[i]);

				//Read the texture
				D3D10_MAPPED_TEXTURE2D mappedTexture2D;
				this->cpuOutputTextures[i]->Map(0, D3D10_MAP_READ, 0, &mappedTexture2D);
				this->cpuOutputTextureData[i] = (float*) mappedTexture2D.pData;
			}
		}

		IEnumerable<Intersection^>^ DirectXRenderAccelerator::FindPrimaryIntersections(Scene^ scene, RenderRegion^ renderRegion)
		{
			List<Intersection^>^ intersections = gcnew List<Intersection^>();

			for(int i=0;i<renderRegion->Height;i++)
			{
				for(int j=0;j<renderRegion->Width;j++)
				{
					float red   = *(this->cpuOutputTextureData[0] + (((renderRegion->PositionY + i) * CoreRayContext::Current->RenderSettings->RenderWidth * 4) + ((renderRegion->PositionX + j) * 4)));
					float green = *(this->cpuOutputTextureData[0] + (((renderRegion->PositionY + i) * CoreRayContext::Current->RenderSettings->RenderWidth * 4) + ((renderRegion->PositionX + j) * 4)) + 1);
					float blue  = *(this->cpuOutputTextureData[0] + (((renderRegion->PositionY + i) * CoreRayContext::Current->RenderSettings->RenderWidth * 4) + ((renderRegion->PositionX + j) * 4)) + 2);
					float alpha = *(this->cpuOutputTextureData[0] + (((renderRegion->PositionY + i) * CoreRayContext::Current->RenderSettings->RenderWidth * 4) + ((renderRegion->PositionX + j) * 4)) + 3);

					float red2   = *(this->cpuOutputTextureData[1] + (((renderRegion->PositionY + i) * CoreRayContext::Current->RenderSettings->RenderWidth * 4) + ((renderRegion->PositionX + j) * 4)));
					float green2 = *(this->cpuOutputTextureData[1] + (((renderRegion->PositionY + i) * CoreRayContext::Current->RenderSettings->RenderWidth * 4) + ((renderRegion->PositionX + j) * 4)) + 1);
					float blue2  = *(this->cpuOutputTextureData[1] + (((renderRegion->PositionY + i) * CoreRayContext::Current->RenderSettings->RenderWidth * 4) + ((renderRegion->PositionX + j) * 4)) + 2);
					float alpha2 = *(this->cpuOutputTextureData[1] + (((renderRegion->PositionY + i) * CoreRayContext::Current->RenderSettings->RenderWidth * 4) + ((renderRegion->PositionX + j) * 4)) + 3);

					if(alpha != 0)
					{
						DifferentialGeometry^ differentialGeometry = gcnew DifferentialGeometry(Vector3(red2,green2,blue2), Vector3(0,0,0), Vector3(0,0,0), Vector3(red,green,blue).Normalize(), Vector2(0,0));
						Sample^ sample = gcnew Sample((float) renderRegion->PositionX + j + 0.5f, (float) renderRegion->PositionY + i + 0.5f);
						
						Intersection^ intersection = gcnew Intersection(differentialGeometry, alpha);

						intersection->Sample = sample;
						intersection->Ray = scene->MainCamera->GenerateRay(sample);

						intersections->Add(intersection);
					}
				}
			}

			return intersections;
		}

		void DirectXRenderAccelerator::CreateDirect3DDevice()
		{
			this->renderTargets = new ID3D10RenderTargetView*[this->renderTargetsCount];
			this->renderTargetTextures = new ID3D10Texture2D*[this->renderTargetsCount];
			this->cpuOutputTextures = new ID3D10Texture2D*[this->renderTargetsCount];
			this->cpuOutputTextureData = new float*[this->renderTargetsCount];

			//Create the Direct3D hardware device
			ID3D10Device* graphicsDevice;

			HRESULT hresult = D3D10CreateDevice(NULL, D3D10_DRIVER_TYPE_HARDWARE, NULL, 0, D3D10_SDK_VERSION, &graphicsDevice);

			if(FAILED(hresult))
			{
				throw gcnew Exception("Direct3D: " + hresult);
			}

			this->graphicsDevice = graphicsDevice;

			//Create render targets
			for(int i = 0; i < this->renderTargetsCount; i++)
			{
				//Create the render target textures
				this->renderTargetTextures[i]  = this->CreateDirect3DTexture(CoreRayContext::Current->RenderSettings->RenderWidth, CoreRayContext::Current->RenderSettings->RenderHeight, true, false, true);

				//Create the hardware render target
				ID3D10RenderTargetView* renderTargetView;
				hresult = this->graphicsDevice->CreateRenderTargetView(this->renderTargetTextures[i], NULL, &renderTargetView);

				if(FAILED(hresult))
				{
					throw gcnew Exception("Direct3D: " + hresult);
				}

				this->renderTargets[i] = renderTargetView;
			}

			//Create the depth stencil buffer
			ID3D10Texture2D* depthStencilTexture  = this->CreateDirect3DTexture(CoreRayContext::Current->RenderSettings->RenderWidth, CoreRayContext::Current->RenderSettings->RenderHeight, false, true, true);

			D3D10_DEPTH_STENCIL_VIEW_DESC depthStencilViewDescription;
				
			depthStencilViewDescription.ViewDimension = D3D10_DSV_DIMENSION_TEXTURE2D;
			depthStencilViewDescription.Texture2D.MipSlice = 0;
			depthStencilViewDescription.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;

			ID3D10DepthStencilView* depthStencilView;
			hresult = this->graphicsDevice->CreateDepthStencilView(depthStencilTexture, &depthStencilViewDescription, &depthStencilView);

			this->depthStencil = depthStencilView;

			if(FAILED(hresult))
			{
				throw gcnew Exception("Direct3D: " + hresult);
			}

			//Create the staging output texture
			for(int i = 0; i < this->renderTargetsCount; i++)
			{
				this->cpuOutputTextures[i] = this->CreateDirect3DTexture(CoreRayContext::Current->RenderSettings->RenderWidth, CoreRayContext::Current->RenderSettings->RenderHeight, false, false, false);
			}

			//Bind the render target to the device
			this->graphicsDevice->OMSetRenderTargets(this->renderTargetsCount, this->renderTargets, this->depthStencil);

			//Setup the output viewport
			D3D10_VIEWPORT viewport;

			viewport.Width = CoreRayContext::Current->RenderSettings->RenderWidth;
			viewport.Height = CoreRayContext::Current->RenderSettings->RenderHeight;
			viewport.MinDepth = 0.0f;
			viewport.MaxDepth = 1.0f;
			viewport.TopLeftX = 0;
			viewport.TopLeftY = 0;

			this->graphicsDevice->RSSetViewports(1, &viewport);
		}

		ID3D10Texture2D* DirectXRenderAccelerator::CreateDirect3DTexture(int width, int height, bool isRenderTarget, bool isDepthStencil, bool isHardware)
		{
			ID3D10Texture2D* textureResource;

			//Fill the texture description texture
			D3D10_TEXTURE2D_DESC textureDescription;

			textureDescription.Width = width;
			textureDescription.Height = height;
			textureDescription.MipLevels = 1;
			textureDescription.ArraySize = 1;
			textureDescription.SampleDesc.Count = 1;
			textureDescription.SampleDesc.Quality = 0;
			textureDescription.BindFlags = 0;
			textureDescription.MiscFlags = 0;

			if(isDepthStencil == false)
			{
				textureDescription.Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
			}

			else
			{
				textureDescription.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
				textureDescription.BindFlags = D3D10_BIND_DEPTH_STENCIL;
			}

			if(isRenderTarget)
			{
				textureDescription.BindFlags = D3D10_BIND_RENDER_TARGET;
			}

			if(isHardware)
			{
				textureDescription.Usage = D3D10_USAGE_DEFAULT;
				textureDescription.CPUAccessFlags = 0;
			}

			else
			{
				textureDescription.Usage = D3D10_USAGE_STAGING;
				textureDescription.CPUAccessFlags = D3D10_CPU_ACCESS_READ;
			}

			//Create the texture
			HRESULT hresult = this->graphicsDevice->CreateTexture2D(&textureDescription, NULL, &textureResource);

			if(FAILED(hresult))
			{
				throw gcnew Exception("Direct3D: " + hresult);
			}

			return textureResource;
		}

		ID3D10Effect* DirectXRenderAccelerator::CreateShader(String^ sourceCode)
		{
			ID3D10Effect* effect;
			ID3D10Blob* errorMessages;

			//Convert source code string
			LPSTR shaderSourceCode = (LPSTR) Marshal::StringToHGlobalAnsi(sourceCode).ToPointer();
			int shaderSourceCodeLength = sourceCode->Length;

			DWORD shaderFlags = D3D10_SHADER_ENABLE_STRICTNESS;

			//Create the include handler class
			HRESULT hresult = D3DX10CreateEffectFromMemory(shaderSourceCode, shaderSourceCodeLength, NULL, NULL, NULL, "fx_4_0", shaderFlags, 0, graphicsDevice, NULL, NULL, &effect, &errorMessages, NULL);

			//Deallocate the converted source code string
			Marshal::FreeHGlobal(IntPtr(shaderSourceCode));

			if(FAILED(hresult))
			{
				if(errorMessages)
				{
					throw gcnew Exception(gcnew String((LPSTR) errorMessages->GetBufferPointer()));
				}

				else
				{
					throw gcnew Exception("Direct3D: " + hresult);
				}
			}

			return effect;
		}
	}
}